﻿using System.Collections.Generic;
using System.Linq;
using Fabrication.Exceptions;
using Fabrication.Tests.TestClasses;
using NUnit.Framework;

namespace Fabrication.Tests
{
    [TestFixture]
    public class general_rules
    {
        [Test]
        public void should_fail_if_no_values_supplied()
        {
            var options = new FabricationScope().Setup<SinglePropertyClass<string>>();
            Assert.Throws<ValueProviderException>(() => options.For(x => x.Value).PickFrom());
        }

        [Test]
        public void should_pick_single_value_if_only_one_supplied()
        {
            var generatedValues = new FabricationScope().Setup<SinglePropertyClass<string>>()
                .For(x => x.Value).PickFrom("Single")
                .Generate(2)
                .Select(x => x.Value);

            Assert.That(generatedValues, Has.All.EqualTo("Single"));
        }

        [Test]
        public void should_pick_random_values_from_supplied_list()
        {
            var values = new List<string> { "First", "Second", "Third", "Fourth", "Fifth" };
            var generatedValues = new FabricationScope().Setup<SinglePropertyClass<string>>()
                .For(x => x.Value).PickFrom(values)
                .Generate(10)
                .Select(x => x.Value);

            // TODO: Ugh. Find nicer fluent NUnit method for subset assertion
            Assert.That(generatedValues, Has.All.Matches<string>(values.Contains));
        }

        [Test]
        public void should_pick_random_values_from_supplied_parameters()
        {
            var generatedValues = new FabricationScope().Setup<SinglePropertyClass<string>>()
                .For(x => x.Value).PickFrom("First", "Second", "Third")
                .Generate(2)
                .Select(x => x.Value);

            // TODO: Ugh. Find nicer fluent NUnit method for subset assertion
            Assert.That(generatedValues, Has.All.Matches<string>(new List<string> { "First", "Second", "Third" }.Contains));
        }

        [Test]
        public void should_pick_distinct_values_from_supplied_list()
        {
            var values = new List<string> { "First", "Second", "Third", "Fourth", "Fifth" };
            var generatedValues = new FabricationScope().Setup<SinglePropertyClass<string>>()
                .For(x => x.Value).PickFromDistinct(values)
                .Generate(3)
                .Select(x => x.Value);

            Assert.That(generatedValues, Is.All.Unique);
        }

        [Test]
        public void should_pick_distinct_values_from_supplied_parameters()
        {
            var generatedValues = new FabricationScope().Setup<SinglePropertyClass<string>>()
                .For(x => x.Value).PickFromDistinct("First", "Second", "Third")
                .Generate(3)
                .Select(x => x.Value);

            Assert.That(generatedValues, Is.All.Unique);
        }

        [Test]
        public void should_fail_if_not_enough_distinct_values_for_number_of_generated_items()
        {
            var fabricationScope = new FabricationScope();
            fabricationScope.Setup<SinglePropertyClass<string>>()
                .For(x => x.Value).PickFromDistinct("First", "Second");

            Assert.Throws<ValueProviderException>(() => fabricationScope.Generate<SinglePropertyClass<string>>(3));
        }

        [Test]
        public void should_not_fail_when_more_distinct_values_than_generated_items()
        {
            var builtItems = new FabricationScope().Setup<SinglePropertyClass<string>>()
                .For(x => x.Value).PickFromDistinct("First", "Second", "Third", "Fourth", "Fifth")
                .Generate(4);

            var distinctValueCount = builtItems.Select(x => x.Value).Distinct().Count();

            Assert.That(distinctValueCount, Is.EqualTo(4));
        }

        [Test]
        public void should_pick_all_values_from_supplied_list()
        {
            var values = new List<string> { "First", "Second", "Third", "Fourth", "Fifth" };
            var builtItems = new FabricationScope().Setup<SinglePropertyClass<string>>()
                .For(x => x.Value).PickFromAll(values)
                .Generate(6);

            var distinctValueCount = builtItems.Select(x => x.Value).Distinct().Count();

            Assert.That(distinctValueCount, Is.EqualTo(5));
        }

        [Test]
        public void should_pick_all_values_from_supplied_parameters()
        {
            var builtItems = new FabricationScope().Setup<SinglePropertyClass<int>>()
                .For(x => x.Value).PickFromAll(10, 15, 20)
                .Generate(3);

            var distinctValueCount = builtItems.Select(x => x.Value).Distinct().Count();

            Assert.That(distinctValueCount, Is.EqualTo(3));
        }

        [Test]
        public void should_resolve_expression_referencing_multiple_properties()
        {
            var builtItem = new FabricationScope().Setup<AlpabeticalProperties>()
                .For(x => x.A).Use("ValueA")
                .For(x => x.B).Use("ValueB")
                .For(x => x.C).Use(x => x.A + ":" + x.B)
                .Generate();


            Assert.That(builtItem.C, Is.EqualTo("ValueA:ValueB"));
        }

        [Test]
        public void should_resolve_in_order_if_expression_has_multiple_dependencies()
        {
            var builtItem = new FabricationScope().Setup<AlpabeticalProperties>()
                .For(x => x.B).Use("ValueB")
                .For(x => x.C).Use("ValueC")
                .For(x => x.A).Use(x => x.B + ":" + x.C)
                .Generate();

            Assert.That(builtItem.A, Is.EqualTo("ValueB:ValueC"));
        }

        class ClassWithReadOnlyProperty
        {
            public string ReadOnlyProperty { get { return "bar"; } }
        }

        [Test]
        public void should_fail_if_rule_applied_to_readonly_property()
        {
            var options = new FabricationScope().Setup<ClassWithReadOnlyProperty>();

            Assert.Throws<PropertySelectorException>(() => options.For(x => x.ReadOnlyProperty).Use("foo"));
        }

        [Test]
        public void should_fail_if_expression_is_self()
        {
            var options = new FabricationScope().Setup<AlpabeticalProperties>();

            Assert.Throws<SelfReferenceException>(() => options.For(x => x.A).Use(x => x.A));
        }

        [Test]
        public void should_fail_if_expression_includes_self()
        {
            var options = new FabricationScope().Setup<AlpabeticalProperties>();

            Assert.Throws<SelfReferenceException>(() => options.For(x => x.A).Use(x => x.A + ":" + x.B));
        }

        [Test]
        public void should_fail_if_expression_includes_self_on_rhs_of_expression()
        {
            var options = new FabricationScope().Setup<AlpabeticalProperties>();

            Assert.Throws<SelfReferenceException>(() => options.For(x => x.A).Use(x => x.B + ":" + x.A));
        }
    }
}
